// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/renderer_host/pepper/pepper_vpn_provider_message_filter_chromeos.h"

#include "base/memory/ptr_util.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/pepper_vpn_provider_resource_host_proxy.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/site_instance.h"
#include "content/public/common/content_client.h"
#include "ppapi/host/dispatch_host_message.h"
#include "ppapi/proxy/ppapi_messages.h"

namespace {

// Shared memory buffer configuration.
const size_t kMaxBufferedPackets = 128;
const size_t kMaxPacketSize = 2048; // 2 KB
const size_t kBufferSize = kMaxBufferedPackets * kMaxPacketSize; // 256 KB

class PepperVpnProviderResourceHostProxyImpl
    : public content::PepperVpnProviderResourceHostProxy {
public:
    explicit PepperVpnProviderResourceHostProxyImpl(
        base::WeakPtr<content::PepperVpnProviderMessageFilter>
            vpn_message_filter);

    void SendOnPacketReceived(const std::vector<char>& packet) override;
    void SendOnUnbind() override;

private:
    base::WeakPtr<content::PepperVpnProviderMessageFilter> vpn_message_filter_;

    DISALLOW_COPY_AND_ASSIGN(PepperVpnProviderResourceHostProxyImpl);
};

PepperVpnProviderResourceHostProxyImpl::PepperVpnProviderResourceHostProxyImpl(
    base::WeakPtr<content::PepperVpnProviderMessageFilter> vpn_message_filter)
    : vpn_message_filter_(vpn_message_filter)
{
}

void PepperVpnProviderResourceHostProxyImpl::SendOnPacketReceived(
    const std::vector<char>& packet)
{
    if (vpn_message_filter_)
        vpn_message_filter_->SendOnPacketReceived(packet);
}

void PepperVpnProviderResourceHostProxyImpl::SendOnUnbind()
{
    if (vpn_message_filter_)
        vpn_message_filter_->SendOnUnbind();
}

} // namespace

namespace content {

PepperVpnProviderMessageFilter::PepperVpnProviderMessageFilter(
    BrowserPpapiHostImpl* host,
    PP_Instance instance)
    : browser_context_(nullptr)
    , bound_(false)
    , weak_factory_(this)
{
    DCHECK(host);

    document_url_ = host->GetDocumentURLForInstance(instance);

    int render_process_id, unused;
    bool result = host->GetRenderFrameIDsForInstance(instance, &render_process_id, &unused);
    DCHECK(result);

    RenderProcessHost* render_process_host = RenderProcessHost::FromID(render_process_id);
    DCHECK(render_process_host);

    browser_context_ = render_process_host->GetBrowserContext();

    vpn_service_proxy_ = content::GetContentClient()->browser()->GetVpnServiceProxy(
        browser_context_);
}

PepperVpnProviderMessageFilter::~PepperVpnProviderMessageFilter()
{
    if (bound_ && resource_host()) {
        resource_host()->host()->SendUnsolicitedReply(
            resource_host()->pp_resource(), PpapiPluginMsg_VpnProvider_OnUnbind());
    }
}

scoped_refptr<base::TaskRunner>
PepperVpnProviderMessageFilter::OverrideTaskRunnerForMessage(
    const IPC::Message& message)
{
    switch (message.type()) {
    case PpapiHostMsg_VpnProvider_Bind::ID:
    case PpapiHostMsg_VpnProvider_SendPacket::ID:
    case PpapiHostMsg_VpnProvider_OnPacketReceivedReply::ID:
        return BrowserThread::GetTaskRunnerForThread(BrowserThread::UI);
    }
    return nullptr;
}

int32_t PepperVpnProviderMessageFilter::OnResourceMessageReceived(
    const IPC::Message& msg,
    ppapi::host::HostMessageContext* context)
{
    PPAPI_BEGIN_MESSAGE_MAP(PepperVpnProviderMessageFilter, msg)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_VpnProvider_Bind, OnBind)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_VpnProvider_SendPacket,
        OnSendPacket)
    PPAPI_DISPATCH_HOST_RESOURCE_CALL(
        PpapiHostMsg_VpnProvider_OnPacketReceivedReply, OnPacketReceivedReply)
    PPAPI_END_MESSAGE_MAP()
    return PP_ERROR_FAILED;
}

void PepperVpnProviderMessageFilter::SendOnPacketReceived(
    const std::vector<char>& packet)
{
    if (packet.size() == 0 || packet.size() > kMaxPacketSize)
        return;

    uint32_t id;
    if (recv_packet_buffer_ && recv_packet_buffer_->GetAvailable(&id)) {
        recv_packet_buffer_->SetAvailable(id, false);
        DoPacketReceived(packet, id);
    } else {
        received_packets_.push(packet);
    }
}

void PepperVpnProviderMessageFilter::SendOnUnbind()
{
    configuration_name_.clear();
    configuration_id_.clear();
    bound_ = false;

    send_packet_buffer_.reset();
    recv_packet_buffer_.reset();

    if (resource_host()) {
        resource_host()->host()->SendUnsolicitedReply(
            resource_host()->pp_resource(), PpapiPluginMsg_VpnProvider_OnUnbind());
    }
}

int32_t PepperVpnProviderMessageFilter::OnBind(
    ppapi::host::HostMessageContext* context,
    const std::string& configuration_id,
    const std::string& configuration_name)
{
    if (!vpn_service_proxy_)
        return PP_ERROR_FAILED;
    if (!content::GetContentClient()->browser()->IsPepperVpnProviderAPIAllowed(
            browser_context_, document_url_)) {
        LOG(ERROR) << "Host " << document_url_.host()
                   << " cannot use vpnProvider API";
        return PP_ERROR_NOACCESS;
    }

    configuration_id_ = configuration_id;
    configuration_name_ = configuration_name;

    return DoBind(base::Bind(&PepperVpnProviderMessageFilter::OnBindSuccess,
                      weak_factory_.GetWeakPtr(),
                      context->MakeReplyMessageContext()),
        base::Bind(&PepperVpnProviderMessageFilter::OnBindFailure,
            weak_factory_.GetWeakPtr(),
            context->MakeReplyMessageContext()));
}

int32_t PepperVpnProviderMessageFilter::OnSendPacket(
    ppapi::host::HostMessageContext* context,
    uint32_t packet_size,
    uint32_t id)
{
    if (!vpn_service_proxy_)
        return PP_ERROR_FAILED;
    if (packet_size > kMaxPacketSize)
        return PP_ERROR_MESSAGE_TOO_BIG;

    char* packet_pointer = static_cast<char*>(send_packet_buffer_->GetBuffer(id));
    std::vector<char> packet(packet_pointer, packet_pointer + packet_size);

    return DoSendPacket(
        packet, base::Bind(&PepperVpnProviderMessageFilter::OnSendPacketSuccess, weak_factory_.GetWeakPtr(), context->MakeReplyMessageContext(), id),
        base::Bind(&PepperVpnProviderMessageFilter::OnSendPacketFailure,
            weak_factory_.GetWeakPtr(), context->MakeReplyMessageContext(),
            id));
}

int32_t PepperVpnProviderMessageFilter::OnPacketReceivedReply(
    ppapi::host::HostMessageContext* context,
    uint32_t id)
{
    if (!received_packets_.empty()) {
        DoPacketReceived(received_packets_.front(), id);
        received_packets_.pop();
    } else {
        recv_packet_buffer_->SetAvailable(id, true);
    }

    return PP_OK;
}

int32_t PepperVpnProviderMessageFilter::DoBind(
    SuccessCallback success_callback,
    FailureCallback failure_callback)
{
    // Initialize shared memory
    if (!send_packet_buffer_ || !recv_packet_buffer_) {
        std::unique_ptr<base::SharedMemory> send_buffer(new base::SharedMemory);
        std::unique_ptr<base::SharedMemory> recv_buffer(new base::SharedMemory);

        if (!send_buffer->CreateAndMapAnonymous(kBufferSize) || !recv_buffer->CreateAndMapAnonymous(kBufferSize))
            return PP_ERROR_NOMEMORY;

        send_packet_buffer_ = base::MakeUnique<ppapi::VpnProviderSharedBuffer>(
            kMaxBufferedPackets, kMaxPacketSize, std::move(send_buffer));
        recv_packet_buffer_ = base::MakeUnique<ppapi::VpnProviderSharedBuffer>(
            kMaxBufferedPackets, kMaxPacketSize, std::move(recv_buffer));
    }

    vpn_service_proxy_->Bind(
        document_url_.host(), configuration_id_, configuration_name_,
        success_callback, failure_callback,
        base::MakeUnique<PepperVpnProviderResourceHostProxyImpl>(
            weak_factory_.GetWeakPtr()));

    return PP_OK_COMPLETIONPENDING;
}

void PepperVpnProviderMessageFilter::OnBindSuccess(
    const ppapi::host::ReplyMessageContext& context)
{
    bound_ = true;

    context.params.AppendHandle(ppapi::proxy::SerializedHandle(
        send_packet_buffer_->GetHandle(), kBufferSize));
    context.params.AppendHandle(ppapi::proxy::SerializedHandle(
        recv_packet_buffer_->GetHandle(), kBufferSize));

    OnBindReply(context, PP_OK);
}

void PepperVpnProviderMessageFilter::OnBindFailure(
    const ppapi::host::ReplyMessageContext& context,
    const std::string& error_name,
    const std::string& error_message)
{
    LOG(ERROR) << "PepperVpnProviderMessageFilter::OnBindFailure(): "
               << "error_name: "
               << "\"" << error_name << "\", "
               << "error_message: "
               << "\"" << error_message << "\"";

    // Clear buffers on error.
    send_packet_buffer_.reset();
    recv_packet_buffer_.reset();

    OnBindReply(context, PP_ERROR_FAILED);
}

void PepperVpnProviderMessageFilter::OnBindReply(
    const ppapi::host::ReplyMessageContext& context,
    int32_t reply)
{
    SendReply(context, PpapiPluginMsg_VpnProvider_BindReply(kMaxBufferedPackets, kMaxPacketSize, reply));
}

int32_t PepperVpnProviderMessageFilter::DoSendPacket(
    const std::vector<char>& packet,
    SuccessCallback success_callback,
    FailureCallback failure_callback)
{
    vpn_service_proxy_->SendPacket(document_url_.host(), packet, success_callback,
        failure_callback);
    return PP_OK_COMPLETIONPENDING;
}

void PepperVpnProviderMessageFilter::OnSendPacketSuccess(
    const ppapi::host::ReplyMessageContext& context,
    uint32_t id)
{
    OnSendPacketReply(context, id);
}

void PepperVpnProviderMessageFilter::OnSendPacketFailure(
    const ppapi::host::ReplyMessageContext& context,
    uint32_t id,
    const std::string& error_name,
    const std::string& error_message)
{
    LOG(ERROR) << "PepperVpnProviderMessageFilter::OnSendPacketFailure(): "
               << "error_name: "
               << "\"" << error_name << "\", "
               << "error_message: "
               << "\"" << error_message << "\"";
    OnSendPacketReply(context, id);
}

void PepperVpnProviderMessageFilter::OnSendPacketReply(
    const ppapi::host::ReplyMessageContext& context,
    uint32_t id)
{
    SendReply(context, PpapiPluginMsg_VpnProvider_SendPacketReply(id));
}

void PepperVpnProviderMessageFilter::DoPacketReceived(
    const std::vector<char>& packet,
    uint32_t id)
{
    uint32_t packet_size = base::checked_cast<uint32_t>(packet.size());
    DCHECK_GT(packet_size, 0U);

    const void* packet_pointer = &packet.front();
    memcpy(recv_packet_buffer_->GetBuffer(id), packet_pointer, packet_size);

    if (resource_host()) {
        resource_host()->host()->SendUnsolicitedReply(
            resource_host()->pp_resource(),
            PpapiPluginMsg_VpnProvider_OnPacketReceived(packet_size, id));
    }
}

} // namespace content
